package com.cloudree.gllib.shape;

import android.opengl.GLES20;
import android.opengl.Matrix;

import com.cloudree.gllib.BaseApp;
import com.cloudree.gllib.R;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

/**
 * Created by Administrator on 2017/8/2.
 */
public class FilterCube extends Shape {
    static final float[] vertexData =
            {
                    // In OpenGL counter-clockwise winding is default. This means that when we look at a triangle,
                    // if the points are counter-clockwise we are looking at the "front". If not we are looking at
                    // the back. OpenGL has an optimization where all back-facing triangles are culled, since they
                    // usually represent the backside of an object and aren't visible anyways.

                    // Front face
                    -1.0f, 1.0f, 1.0f,
                    -1.0f, -1.0f, 1.0f,
                    1.0f, 1.0f, 1.0f,
                    -1.0f, -1.0f, 1.0f,
                    1.0f, -1.0f, 1.0f,
                    1.0f, 1.0f, 1.0f,

                    // Right face
                    1.0f, 1.0f, 1.0f,
                    1.0f, -1.0f, 1.0f,
                    1.0f, 1.0f, -1.0f,
                    1.0f, -1.0f, 1.0f,
                    1.0f, -1.0f, -1.0f,
                    1.0f, 1.0f, -1.0f,

                    // Back face
                    1.0f, 1.0f, -1.0f,
                    1.0f, -1.0f, -1.0f,
                    -1.0f, 1.0f, -1.0f,
                    1.0f, -1.0f, -1.0f,
                    -1.0f, -1.0f, -1.0f,
                    -1.0f, 1.0f, -1.0f,

                    // Left face
                    -1.0f, 1.0f, -1.0f,
                    -1.0f, -1.0f, -1.0f,
                    -1.0f, 1.0f, 1.0f,
                    -1.0f, -1.0f, -1.0f,
                    -1.0f, -1.0f, 1.0f,
                    -1.0f, 1.0f, 1.0f,

                    // Top face
                    -1.0f, 1.0f, -1.0f,
                    -1.0f, 1.0f, 1.0f,
                    1.0f, 1.0f, -1.0f,
                    -1.0f, 1.0f, 1.0f,
                    1.0f, 1.0f, 1.0f,
                    1.0f, 1.0f, -1.0f,

                    // Bottom face
                    1.0f, -1.0f, -1.0f,
                    1.0f, -1.0f, 1.0f,
                    -1.0f, -1.0f, -1.0f,
                    1.0f, -1.0f, 1.0f,
                    -1.0f, -1.0f, 1.0f,
                    -1.0f, -1.0f, -1.0f,
            };


    // X, Y, Z
    // The normal is used in light calculations and is a vector which points
    // orthogonal to the plane of the surface. For a cube model, the normals
    // should be orthogonal to the points of each face.
    static final float[] normalData =
            {
                    // Front face
                    0.0f, 0.0f, 1.0f,
                    0.0f, 0.0f, 1.0f,
                    0.0f, 0.0f, 1.0f,
                    0.0f, 0.0f, 1.0f,
                    0.0f, 0.0f, 1.0f,
                    0.0f, 0.0f, 1.0f,

                    // Right face
                    1.0f, 0.0f, 0.0f,
                    1.0f, 0.0f, 0.0f,
                    1.0f, 0.0f, 0.0f,
                    1.0f, 0.0f, 0.0f,
                    1.0f, 0.0f, 0.0f,
                    1.0f, 0.0f, 0.0f,

                    // Back face
                    0.0f, 0.0f, -1.0f,
                    0.0f, 0.0f, -1.0f,
                    0.0f, 0.0f, -1.0f,
                    0.0f, 0.0f, -1.0f,
                    0.0f, 0.0f, -1.0f,
                    0.0f, 0.0f, -1.0f,

                    // Left face
                    -1.0f, 0.0f, 0.0f,
                    -1.0f, 0.0f, 0.0f,
                    -1.0f, 0.0f, 0.0f,
                    -1.0f, 0.0f, 0.0f,
                    -1.0f, 0.0f, 0.0f,
                    -1.0f, 0.0f, 0.0f,

                    // Top face
                    0.0f, 1.0f, 0.0f,
                    0.0f, 1.0f, 0.0f,
                    0.0f, 1.0f, 0.0f,
                    0.0f, 1.0f, 0.0f,
                    0.0f, 1.0f, 0.0f,
                    0.0f, 1.0f, 0.0f,

                    // Bottom face
                    0.0f, -1.0f, 0.0f,
                    0.0f, -1.0f, 0.0f,
                    0.0f, -1.0f, 0.0f,
                    0.0f, -1.0f, 0.0f,
                    0.0f, -1.0f, 0.0f,
                    0.0f, -1.0f, 0.0f
            };

    private FloatBuffer vertexBuffer;
    private FloatBuffer normalBuffer;
    private FloatBuffer textureBuffer;
    private FloatBuffer planeBuffer;

    private int mLightProgram;
    private int mAimTexture;
    private int mPlaneTexture;

    //texture related
    final static float[] textureData =
            {
                    // Front face
                    0.0f, 0.0f,
                    0.0f, 1.0f,
                    1.0f, 0.0f,
                    0.0f, 1.0f,
                    1.0f, 1.0f,
                    1.0f, 0.0f,

                    // Right face
                    0.0f, 0.0f,
                    0.0f, 1.0f,
                    1.0f, 0.0f,
                    0.0f, 1.0f,
                    1.0f, 1.0f,
                    1.0f, 0.0f,

                    // Back face
                    0.0f, 0.0f,
                    0.0f, 1.0f,
                    1.0f, 0.0f,
                    0.0f, 1.0f,
                    1.0f, 1.0f,
                    1.0f, 0.0f,

                    // Left face
                    0.0f, 0.0f,
                    0.0f, 1.0f,
                    1.0f, 0.0f,
                    0.0f, 1.0f,
                    1.0f, 1.0f,
                    1.0f, 0.0f,

                    // Top face
                    0.0f, 0.0f,
                    0.0f, 1.0f,
                    1.0f, 0.0f,
                    0.0f, 1.0f,
                    1.0f, 1.0f,
                    1.0f, 0.0f,

                    // Bottom face
                    0.0f, 0.0f,
                    0.0f, 1.0f,
                    1.0f, 0.0f,
                    0.0f, 1.0f,
                    1.0f, 1.0f,
                    1.0f, 0.0f
            };
    static final float[] planeData =
            {
                    // Front face
                    0.0f, 0.0f,
                    0.0f, 25.0f,
                    25.0f, 0.0f,
                    0.0f, 25.0f,
                    25.0f, 25.0f,
                    25.0f, 0.0f,

                    // Right face
                    0.0f, 0.0f,
                    0.0f, 25.0f,
                    25.0f, 0.0f,
                    0.0f, 25.0f,
                    25.0f, 25.0f,
                    25.0f, 0.0f,

                    // Back face
                    0.0f, 0.0f,
                    0.0f, 25.0f,
                    25.0f, 0.0f,
                    0.0f, 25.0f,
                    25.0f, 25.0f,
                    25.0f, 0.0f,

                    // Left face
                    0.0f, 0.0f,
                    0.0f, 25.0f,
                    25.0f, 0.0f,
                    0.0f, 25.0f,
                    25.0f, 25.0f,
                    25.0f, 0.0f,

                    // Top face
                    0.0f, 0.0f,
                    0.0f, 25.0f,
                    25.0f, 0.0f,
                    0.0f, 25.0f,
                    25.0f, 25.0f,
                    25.0f, 0.0f,

                    // Bottom face
                    0.0f, 0.0f,
                    0.0f, 25.0f,
                    25.0f, 0.0f,
                    0.0f, 25.0f,
                    25.0f, 25.0f,
                    25.0f, 0.0f
            };

    private int mMinFilter;
    private int mMagFilter;

    @Override
    public void loadingMaterial() {
        vertexBuffer = ByteBuffer.allocateDirect(vertexData.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer().put(vertexData);
        vertexBuffer.position(0);
        normalBuffer = ByteBuffer.allocateDirect(normalData.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer().put(normalData);
        normalBuffer.position(0);
        textureBuffer = ByteBuffer.allocateDirect(textureData.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer().put(textureData);
        textureBuffer.position(0);
        planeBuffer = ByteBuffer.allocateDirect(planeData.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer().put(planeData);
        planeBuffer.position(0);
    }

    @Override
    public void compileShader() {
        int vertexShader = generateShader(GLES20.GL_VERTEX_SHADER, getVertexShaderCode());
        int fragmentShader = generateShader(GLES20.GL_FRAGMENT_SHADER, getFragmentShaderCode());
        String[] attrs = new String[]{"aPosition", "aNormal", "aTexCoordinate"};
        mProgram = generateGlProgram(vertexShader, fragmentShader, attrs);

        //generate light program
        String lightVertexShaderCode = readRawFile(BaseApp.getApp(), R.raw.light_vertex_shader);
        String lightFragmentShaderCode = readRawFile(BaseApp.getApp(), R.raw.light_fragment_shader);
        int lightVertexShader = generateShader(GLES20.GL_VERTEX_SHADER, lightVertexShaderCode);
        int lightFragmentShader = generateShader(GLES20.GL_FRAGMENT_SHADER, lightFragmentShaderCode);
        String[] lightAttrs = new String[]{"aPosition"};
        mLightProgram = generateGlProgram(lightVertexShader, lightFragmentShader, lightAttrs);

        //load texture
        mAimTexture = loadTexture(BaseApp.getApp(), R.mipmap.table);
        GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);

        mPlaneTexture = loadTexture(BaseApp.getApp(), R.mipmap.grass);
        GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);


    }

    @Override
    public void draw(float[] mvpMatrix, float[] modelMatrix, float[] viewMatrix, float[] projectionMatrix) {

    }

    /**
     * draw shape with light
     */
    public void draw(float[] mvpMatrix, float[] modelMatrix, float[] viewMatrix, float[] projectionMatrix, float[] lightModeMatrix, float[] lightEyeSpace, float[] lightModelSpace, float[] mTemporaryMatrix) {

        //bind attribute
        int mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
        int mMVMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVMatrix");
        int mLightPositionHandle = GLES20.glGetUniformLocation(mProgram, "uLightPosition");

        int mPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
        int mNormalHandle = GLES20.glGetAttribLocation(mProgram, "aNormal");

        //set filter
        if (mMinFilter != 0) setMinFilter(mMinFilter);
        if (mMagFilter != 0) setMagFilter(mMagFilter);


        //allocate drawing order
        vertexBuffer.position(0);
        GLES20.glVertexAttribPointer(mPositionHandle, 3, GLES20.GL_FLOAT, false, 0, vertexBuffer);
        GLES20.glEnableVertexAttribArray(mPositionHandle);

        normalBuffer.position(0);
        GLES20.glVertexAttribPointer(mNormalHandle, 3, GLES20.GL_FLOAT, false, 0, normalBuffer);
        GLES20.glEnableVertexAttribArray(mNormalHandle);
        //combine matrix
        // (which currently contains model * view).
        Matrix.multiplyMM(mvpMatrix, 0, viewMatrix, 0, modelMatrix, 0);

        // Pass in the modelview matrix.
        GLES20.glUniformMatrix4fv(mMVMatrixHandle, 1, false, mvpMatrix, 0);

        // This multiplies the modelview matrix by the projection matrix, and stores the result in the MVP matrix
        // (which now contains model * view * projection).
        Matrix.multiplyMM(mTemporaryMatrix, 0, projectionMatrix, 0, mvpMatrix, 0);
        System.arraycopy(mTemporaryMatrix, 0, mvpMatrix, 0, 16);

        // Pass in the combined matrix.
        GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);

        // Pass in the light position in eye space.
        GLES20.glUniform3f(mLightPositionHandle, lightEyeSpace[0], lightEyeSpace[1], lightEyeSpace[2]);

        // Draw the cube.
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 36);

        //draw
    }

    /**
     * draw light
     */
    public void drawLight(float[] mvpMatrix, float[] viewMatrix, float[] projectionMatrix, float[] lightModeMatrix, float[] lightModelSpace, float[] mTemporaryMatrix) {
        //draw light
        GLES20.glUseProgram(mLightProgram);
        int lightMVPMatrixHandle = GLES20.glGetUniformLocation(mLightProgram, "uMVPMatrix");
        int lightPositionHandle = GLES20.glGetAttribLocation(mLightProgram, "aPosition");
        // Pass in the position.
        GLES20.glVertexAttrib3f(lightPositionHandle, lightModelSpace[0], lightModelSpace[1], lightModelSpace[2]);
        GLES20.glDisableVertexAttribArray(lightPositionHandle);
        // Pass in the transformation matrix.
        Matrix.multiplyMM(mvpMatrix, 0, viewMatrix, 0, lightModeMatrix, 0);
        Matrix.multiplyMM(mTemporaryMatrix, 0, projectionMatrix, 0, mvpMatrix, 0);
        System.arraycopy(mTemporaryMatrix, 0, mvpMatrix, 0, 16);
        GLES20.glUniformMatrix4fv(lightMVPMatrixHandle, 1, false, mvpMatrix, 0);

        // Draw the point.
        GLES20.glDrawArrays(GLES20.GL_POINTS, 0, 1);
    }

    /**
     * draw aim shape
     */
    public void drawAim(float[] mvpMatrix, float[] modelMatrix, float[] viewMatrix, float[] projectionMatrix, float[] lightModeMatrix, float[] lightEyeSpace, float[] lightModelSpace, float[] mTemporaryMatrix) {
        //draw cube
        GLES20.glUseProgram(mProgram);
        int mTextureHandle = GLES20.glGetUniformLocation(mProgram, "uTexture");
        int mTexCoordinationHandle = GLES20.glGetAttribLocation(mProgram, "aTexCoordinate");
        textureBuffer.position(0);
        GLES20.glVertexAttribPointer(mTexCoordinationHandle, 2, GLES20.GL_FLOAT, false, 0, textureBuffer);
        GLES20.glEnableVertexAttribArray(mTexCoordinationHandle);

        // Set the active texture unit to texture unit 0.
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);

        // Bind the texture to this unit.
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mAimTexture);

        // Tell the texture uniform sampler to use this texture in the shader by binding to texture unit 0.
        GLES20.glUniform1i(mTextureHandle, 0);

        draw(mvpMatrix, modelMatrix, viewMatrix, projectionMatrix, lightModeMatrix, lightEyeSpace, lightModelSpace, mTemporaryMatrix);

    }

    /**
     * draw aim shape
     */
    public void drawPlane(float[] mvpMatrix, float[] modelMatrix, float[] viewMatrix, float[] projectionMatrix, float[] lightModeMatrix, float[] lightEyeSpace, float[] lightModelSpace, float[] mTemporaryMatrix) {
        //draw cube
        GLES20.glUseProgram(mProgram);
        int mTextureHandle = GLES20.glGetUniformLocation(mProgram, "uTexture");
        int mTexCoordinationHandle = GLES20.glGetAttribLocation(mProgram, "aTexCoordinate");
        planeBuffer.position(0);
        GLES20.glVertexAttribPointer(mTexCoordinationHandle, 2, GLES20.GL_FLOAT, false, 0, planeBuffer);
        GLES20.glEnableVertexAttribArray(mTexCoordinationHandle);

        // Set the active texture unit to texture unit 0.
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);

        // Bind the texture to this unit.
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mPlaneTexture);

        // Tell the texture uniform sampler to use this texture in the shader by binding to texture unit 0.
        GLES20.glUniform1i(mTextureHandle, 0);

        draw(mvpMatrix, modelMatrix, viewMatrix, projectionMatrix, lightModeMatrix, lightEyeSpace, lightModelSpace, mTemporaryMatrix);

    }

    @Override
    public String getVertexShaderCode() {
        return readRawFile(BaseApp.getApp(), R.raw.filter_vertex_shader);
    }

    @Override
    public String getFragmentShaderCode() {
        return readRawFile(BaseApp.getApp(), R.raw.filter_fragment_shader);
    }


    /**
     * set filter
     */
    public void setMinFilter(int filter) {
        if (mAimTexture != 0 && mPlaneTexture != 0) {
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mAimTexture);
            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, filter);
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mPlaneTexture);
            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, filter);
        } else {
            mMinFilter = filter;
        }
    }

    public void setMagFilter(int filter) {
        if (mAimTexture != 0 && mPlaneTexture != 0) {
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mAimTexture);
            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, filter);
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mPlaneTexture);
            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, filter);
        } else {
            mMinFilter = filter;
        }
    }
}
